package jam4j.lang;

import java.io.File;

import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.regex.Matcher;

import jam4j.Exiting;
import jam4j.Globals;
import jam4j.TargetDef;
import jam4j.util.Globs;
import jam4j.util.Strings;

abstract class Builtins {
    private Builtins() { }

    private static abstract class BuiltinProcedure extends Procedure {
        @Override
        public String toString() {
            return "<built-in procedure \u201c" + 
                    getClass().getSimpleName() + "\u201d>";
        }
    }
    
    private static final class Echo extends BuiltinProcedure {
        String[] call(Context cxt, ScriptTargetDef onTarget, String[] ... args) {
            if (args.length == 0)
                Globals.out().println();
            else
                Globals.out().println(Strings.rightJoin(" ", args[0]));

            return Expression.UNDEF;
        }
    }

    private static final class Exit extends BuiltinProcedure {
        String[] call(Context cxt, ScriptTargetDef onTarget, String[] ... args) {
            if (args.length > 0)
                Globals.out().println(Strings.rightJoin(" ", args[0]));
            throw new Exiting();
        }
    }

    private static final class Match extends BuiltinProcedure {
        String[] call(Context cxt, ScriptTargetDef onTarget, String[] ... args) {
            if (args.length < 2)
                return Expression.UNDEF;
            
            final String[] regexes = args[0];
            final String[] strings = args[1];

            final List<String> ans = new ArrayList<String>();

            for (String regex : regexes) {
                final Pattern pattern;
                try {
                    pattern = Pattern.compile(regex);
                } catch (PatternSyntaxException e) {
                    System.out.println(e.getDescription());
                    // Mimic jam's lax error handling
                    continue;
                }

                for (String string : strings) {
                    final Matcher matcher = pattern.matcher(string);

                    if (!matcher.matches())
                        continue;

                    final int groupCount = matcher.groupCount();
                    for (int groupIx = 1; groupIx <= groupCount; groupIx++)
                        ans.add(matcher.group(groupIx));
                }
            }

            return ans.toArray(new String[ans.size()]);
        }
    }

    private static final class Glob extends BuiltinProcedure {
        private static void scan(
                File dir, String[] globs, Collection<String> matched) {
            final String[] filenames;
            if ((filenames = dir.list()) == null)
                return;
            
            final String dirPath = dir.getPath();
            for (String filename : filenames) {
                for (String glob : globs)
                    if (Globs.matches(filename, glob)) {
                        matched.add(dirPath + File.separatorChar + filename);
                        break;
                    }

                final File subfile = new File(dir, filename);
                if (subfile.isDirectory())
                    scan(subfile, globs, matched);
            }
        }

        String[] call(Context cxt, ScriptTargetDef onTarget, String[] ... args) {
            if (args.length < 2)
                return Expression.UNDEF;

            final String[] dirs = args[0];
            final String[] globs = args[1];

            final List<String> ans = new ArrayList<String>();

            for (String dir : dirs) 
                scan(FilePath.parse(dir).toAbsoluteFile(), globs, ans);
            
            return ans.toArray(new String[ans.size()]);
        }
    }

    private static final class Depends extends BuiltinProcedure {
        private final boolean includes;
        
        private Depends(boolean includes) {
            this.includes = includes;
        }
        
        String[] call(Context cxt, ScriptTargetDef onTarget, String[] ... args) {
            if (args.length < 2)
                return Expression.UNDEF;
            
            final String[] targets = args[0], deps = args[1];
            for (String targetName : targets) {
                final ScriptTargetDef target = cxt.target(targetName);
                for (String depName : deps)
                    (includes ? target.includeTarget() : target)
                            .addDependency(cxt.target(depName));
            }
            
            return Expression.UNDEF;
        }
    }
    
    private static final class SetFlag extends BuiltinProcedure {
        private final TargetDef.Flag flag;
        
        private SetFlag(TargetDef.Flag flag) {
            this.flag = flag;
        }
        
        String[] call(Context cxt, ScriptTargetDef onTarget, String[] ... args) {
            if (args.length != 0)
                for (String targetName : args[0])
                    cxt.target(targetName).addFlag(flag);
            
            return Expression.UNDEF;
        }
    }
    
    private static final class Registration {
        final Map<String, Rule> map;
        Registration(Map<String, Rule> map) {
            this.map = map;
        }
        
        Registration register(String name, BuiltinProcedure proc) {
            map.put(name, new Rule(name, proc));
            return this;
        }
    }
    
    private static final BuiltinProcedure
        ECHO =      new Echo(),
        EXIT =      new Exit(),
        MATCH =     new Match(),
        GLOB =      new Glob(),
        DEPENDS =   new Depends(false),
        INCLUDES =  new Depends(true),
        ALWAYS =    new SetFlag(TargetDef.Flag.TOUCHED),
        LEAVES =    new SetFlag(TargetDef.Flag.LEAVES),
        NOCARE =    new SetFlag(TargetDef.Flag.NOCARE),
        NOTFILE =   new SetFlag(TargetDef.Flag.NOTFILE),
        NOUPDATE =  new SetFlag(TargetDef.Flag.NOUPDATE),
        TEMPORARY = new SetFlag(TargetDef.Flag.TEMP);
    
    static void register(Map<String, Rule> rules) {
        new Registration(rules)
            .register("Depends",    DEPENDS)
            .register("DEPENDS",    DEPENDS)
            
            .register("Includes",   INCLUDES)
            .register("INCLUDES",   INCLUDES)
            
            .register("Always",     ALWAYS)
            .register("ALWAYS",     ALWAYS)
            
            .register("Leaves",     LEAVES)
            .register("LEAVES",     LEAVES)
            
            .register("NoCare",     NOCARE)
            .register("NOCARE",     NOCARE)
            
            .register("NotFile",    NOTFILE)
            .register("NOTFILE",    NOTFILE)
            .register("NOTIME",     NOTFILE)
            
            .register("NoUpdate",   NOUPDATE)
            .register("NOUPDATE",   NOUPDATE)
            
            .register("Temporary",  TEMPORARY)
            .register("TEMPORARY",  TEMPORARY)
            
            .register("echo",       ECHO)
            .register("Echo",       ECHO)
            .register("ECHO",       ECHO)
            
            .register("exit",       EXIT)
            .register("Exit",       EXIT)
            .register("EXIT",       EXIT)
            
            .register("Glob",       GLOB)
            .register("GLOB",       GLOB)
            
            .register("Match",      MATCH)
            .register("MATCH",      MATCH);
    }
}